Car Price prediction¶

Découverte du dataset¶

In [3]:
data.head()
Out[3]:
car_ID symboling CarName fueltype aspiration doornumber carbody drivewheel enginelocation wheelbase ... enginesize fuelsystem boreratio stroke compressionratio horsepower peakrpm citympg highwaympg price
0 1 3 alfa-romero giulia gas std two convertible rwd front 88.6 ... 130 mpfi 3.47 2.68 9.0 111 5000 21 27 13495.0
1 2 3 alfa-romero stelvio gas std two convertible rwd front 88.6 ... 130 mpfi 3.47 2.68 9.0 111 5000 21 27 16500.0
2 3 1 alfa-romero Quadrifoglio gas std two hatchback rwd front 94.5 ... 152 mpfi 2.68 3.47 9.0 154 5000 19 26 16500.0
3 4 2 audi 100 ls gas std four sedan fwd front 99.8 ... 109 mpfi 3.19 3.40 10.0 102 5500 24 30 13950.0
4 5 2 audi 100ls gas std four sedan 4wd front 99.4 ... 136 mpfi 3.19 3.40 8.0 115 5500 18 22 17450.0

5 rows × 26 columns

In [4]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 205 entries, 0 to 204
Data columns (total 26 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   car_ID            205 non-null    int64  
 1   symboling         205 non-null    int64  
 2   CarName           205 non-null    object 
 3   fueltype          205 non-null    object 
 4   aspiration        205 non-null    object 
 5   doornumber        205 non-null    object 
 6   carbody           205 non-null    object 
 7   drivewheel        205 non-null    object 
 8   enginelocation    205 non-null    object 
 9   wheelbase         205 non-null    float64
 10  carlength         205 non-null    float64
 11  carwidth          205 non-null    float64
 12  carheight         205 non-null    float64
 13  curbweight        205 non-null    int64  
 14  enginetype        205 non-null    object 
 15  cylindernumber    205 non-null    object 
 16  enginesize        205 non-null    int64  
 17  fuelsystem        205 non-null    object 
 18  boreratio         205 non-null    float64
 19  stroke            205 non-null    float64
 20  compressionratio  205 non-null    float64
 21  horsepower        205 non-null    int64  
 22  peakrpm           205 non-null    int64  
 23  citympg           205 non-null    int64  
 24  highwaympg        205 non-null    int64  
 25  price             205 non-null    float64
dtypes: float64(8), int64(8), object(10)
memory usage: 41.8+ KB
In [5]:
data.describe()
Out[5]:
car_ID symboling wheelbase carlength carwidth carheight curbweight enginesize boreratio stroke compressionratio horsepower peakrpm citympg highwaympg price
count 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000 205.000000
mean 103.000000 0.834146 98.756585 174.049268 65.907805 53.724878 2555.565854 126.907317 3.329756 3.255415 10.142537 104.117073 5125.121951 25.219512 30.751220 13276.710571
std 59.322565 1.245307 6.021776 12.337289 2.145204 2.443522 520.680204 41.642693 0.270844 0.313597 3.972040 39.544167 476.985643 6.542142 6.886443 7988.852332
min 1.000000 -2.000000 86.600000 141.100000 60.300000 47.800000 1488.000000 61.000000 2.540000 2.070000 7.000000 48.000000 4150.000000 13.000000 16.000000 5118.000000
25% 52.000000 0.000000 94.500000 166.300000 64.100000 52.000000 2145.000000 97.000000 3.150000 3.110000 8.600000 70.000000 4800.000000 19.000000 25.000000 7788.000000
50% 103.000000 1.000000 97.000000 173.200000 65.500000 54.100000 2414.000000 120.000000 3.310000 3.290000 9.000000 95.000000 5200.000000 24.000000 30.000000 10295.000000
75% 154.000000 2.000000 102.400000 183.100000 66.900000 55.500000 2935.000000 141.000000 3.580000 3.410000 9.400000 116.000000 5500.000000 30.000000 34.000000 16503.000000
max 205.000000 3.000000 120.900000 208.100000 72.300000 59.800000 4066.000000 326.000000 3.940000 4.170000 23.000000 288.000000 6600.000000 49.000000 54.000000 45400.000000
In [6]:
data.shape
Out[6]:
(205, 26)

On peut constater qu'on a peu de lignes et beaucoup de colonnes

Data cleaning¶

In [7]:
data.isna().sum()
Out[7]:
car_ID              0
symboling           0
CarName             0
fueltype            0
aspiration          0
doornumber          0
carbody             0
drivewheel          0
enginelocation      0
wheelbase           0
carlength           0
carwidth            0
carheight           0
curbweight          0
enginetype          0
cylindernumber      0
enginesize          0
fuelsystem          0
boreratio           0
stroke              0
compressionratio    0
horsepower          0
peakrpm             0
citympg             0
highwaympg          0
price               0
dtype: int64
In [8]:
data.duplicated().sum()
Out[8]:
0
In [9]:
data['doornumber'].value_counts()
Out[9]:
four    115
two      90
Name: doornumber, dtype: int64
In [11]:
data['doornumber'].value_counts()
Out[11]:
4    115
2     90
Name: doornumber, dtype: int64

On a pu constater qu'il n'y avait que 2 valeurs uniques en chaine de caractères qui correspondait à des chiffres. Pour facilité l'analyse, on a remplacé par les chiffres

Data Visualisation / Analyse¶

In [12]:
sns.heatmap(data.corr(), annot=True, fmt=".1f")
Out[12]:
<AxesSubplot:>

Avec le heatmap on peut voir que plusieurs valeurs semblent impacter le prix de la voiture

In [14]:
sns.scatterplot(data=data, x="price", y="enginesize", size="horsepower").set(title="Prix d'une voiture en fonction de la taille du moteur et de la puissance en chevaux");

Plus le prix augmente, plus la taille des points semble augmenter (ce qui correspond à la puissance en chevaux) La taille du moteur semble aussi augmenter avec le prix, on obtient un graphique qui à l'air linéaire

In [15]:
sns.scatterplot(data=data, x="price", y="carwidth").set(title="Prix d'une voiture en fonction de la largeur de la voiture");

Quand le prix est plus faible, les points sont plus condensés et au même endroit. Jusqu'au prix de 25000 la relation a l'air plutôt linéaire mais au dessus, les points sont plus éparpillés ce qui peut créer des erreurs

In [16]:
sns.scatterplot(data=data, x="price", y="curbweight").set(title="Prix de la voiture en fonction du poids à vide");

On obtient un graphique assez similaire au précédent, où au dessus de 25000 le poids à vide de la voiture ne semble plus avoir trop d'impact

In [18]:
plt.pie(data['fueltype'].value_counts(), labels=data['fueltype'].unique(), autopct='%.0f%%')
plt.title("Pourcentage du type de carburant")
plt.show()

On peut remarquer que 90% des voitures sont au gasoil et donc prendre cette feature en compte et sûrement peu signicative car pas assez de données

In [19]:
sns.scatterplot(data=data, x="price", y="enginesize", hue="curbweight", size="horsepower").set(title= "Prix d'une voiture en fonction de la taille du moteur, la puissance en chevaux et le poids à vide");

On remarque bien que de manière générale le prix augmente bien avec les features, même s'il y a quelques zones où cela se confond

In [20]:
plt.hist(data['price'])
plt.xlabel('Price')
plt.ylabel('Count')
plt.title('Prix des voitures')
plt.show()

On remarque que la majorité des voitures ont un prix inférieur à 25000

In [21]:
plt.hist(data['enginesize'])
plt.xlabel('Enginesize')
plt.ylabel('Count')
plt.title('Distribution de la taille du moteur')
plt.show()
In [22]:
import plotly.express as px
fig = px.scatter_3d(data, x="price", y="enginesize", z="curbweight", color="carwidth", size="horsepower")
fig.show()

Graphique en 3d avec les features analysées précédemment. On remarque bien la forte concentration de points lorsque le prix est faible

Model¶

Première itération avec 1 feature¶

In [23]:
y = data.price
X = data[['enginesize']]
In [31]:
model.score(X_test_scaled, y_test)
Out[31]:
0.7726894661645483

Deuxième itération avec 3 features¶

In [32]:
y_2 = data.price
X_2 = data[['enginesize', 'curbweight', 'horsepower']]
In [37]:
model.score(X_test_scaled_2, y_test_2)
Out[37]:
0.7858734070212768

On remarque que le score est légèrement supérieur

Cross Validation¶

In [39]:
cross_val_score(model, X, y, cv=3)
Out[39]:
array([0.73047413, 0.79655265, 0.44822534])
In [40]:
cross_val_score(model, X_2, y_2, cv=3)
Out[40]:
array([0.7933756 , 0.7612985 , 0.58043235])

Le score est également plus élevé avec les 3 features, comme il y a ~200 lignes dans le dataset on ne peut pas faire énormément de fold

Predictions et erreurs¶

In [42]:
residus = y_predict_2 - y_test_2
sns.histplot(residus, bins=15, kde=True).set(title="Distribution des résidus");

La distribution des résidus semble suivre une loi normale

In [43]:
plt.scatter(y_predict_2, residus, label="residuals")
plt.plot([4000,35000], [0,0], c='r', label="null-error line")
plt.xlabel('Predicted car price')
plt.ylabel('Residuals')
plt.title('Répartition des résidus')
plt.legend()
plt.show()

La répartition des résidus ne semble pas homoscédastique